package jp.dodododo.aop.config;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import jp.dodododo.aop.Aspects;
import jp.dodododo.aop.annotation.Enhance;
import jp.dodododo.aop.impl.AspectImpl;
import jp.dodododo.aop.impl.AspectsImpl;
import jp.dodododo.aop.impl.DefImpl;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.helpers.DefaultHandler;

@Enhance(false)
public class AspectConfigBuilder {

	public static final String PUBLIC_ID = "-//SAMURAI_AOP//DTD samurai-aop 1.0//EN";

	public static final String DTD_PATH10 = "jp/dodododo/aop/dtd/samurai-aop10.dtd";

	private Map<String, String> dtdMap = new HashMap<String, String>();

	private static final Set<String> NO_OUT_INFO_CLASSLOADER_CLASS_NAMES = new HashSet<String>();

	static {
		NO_OUT_INFO_CLASSLOADER_CLASS_NAMES.add("sun.reflect.DelegatingClassLoader");
		NO_OUT_INFO_CLASSLOADER_CLASS_NAMES.add("org.seasar.framework.unit.UnitClassLoader");
		NO_OUT_INFO_CLASSLOADER_CLASS_NAMES.add("org.apache.jasper.servlet.JasperLoader");
	}

	public AspectConfigBuilder() {
		dtdMap.put(PUBLIC_ID, DTD_PATH10);
	}

	public Aspects build(ClassLoader loader, String path) {
		try {
			return parse(loader, path);
		} catch (ConfigNotFoundException e) {
			if (NO_OUT_INFO_CLASSLOADER_CLASS_NAMES.contains(loader.getClass().getName()) == false) {
				System.out.println("INFO : " + e.getMessage() + "    (" + loader + ")");
			}
			return null;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public Aspects build(String path) {
		return build(Thread.currentThread().getContextClassLoader(), path);
	}

	private Aspects parse(ClassLoader loader, String path) throws Exception {
		if (loader == null) {
			return null;
		}
		InputStream is = getInputStream(loader, path);
		if (is == null) {
			return null;
		}
		SAXParserFactory saxFactory = SAXParserFactory.newInstance();
		saxFactory.setValidating(true);
		SAXParser parser = saxFactory.newSAXParser();
		Handler handler = new Handler(dtdMap);
		parser.parse(is, handler);
		return handler.getResult();
	}

	private InputStream getInputStream(ClassLoader loader, String path) {
		final InputStream is = loader.getResourceAsStream(path);
		if (is == null) {
			throw new ConfigNotFoundException(path + " was not found.");
		}
		return is;
	}

	public class Handler extends DefaultHandler {

		private AspectsImpl result = new AspectsImpl();

		Map<String, String> dtdPaths = null;

		private Locator locator;

		public Handler(Map<String, String> dtdMap) {
			dtdPaths = dtdMap;
		}

		public Aspects getResult() {
			return result;
		}

		@Override
		public void setDocumentLocator(Locator locator) {
			this.locator = locator;
		}

		@Override
		public InputSource resolveEntity(String publicId, String systemId) {
			if (publicId != null) {
				String dtdPath = dtdPaths.get(publicId);
				if (dtdPath != null) {
					return createInputSource(getResourceAsStream(dtdPath), systemId);
				}
			}
			if (systemId == null) {
				return null;
			}
			if (systemId.startsWith("file:")) {
				URL url = null;
				try {
					url = new URL(systemId);
				} catch (MalformedURLException e) {
					throw new RuntimeException(e);
				}
				String path = url.getFile();
				if (path.startsWith("/")) {
					path = path.substring(1);
				}
				return createInputSource(getResourceAsStream(path), systemId);
			}

			return new InputSource(getResourceAsStream(systemId));
		}

		private InputStream getResourceAsStream(String path) {
			ClassLoader loader = Thread.currentThread().getContextClassLoader();
			return loader.getResourceAsStream(path);
		}

		private InputSource createInputSource(InputStream stream, String systemId) {
			InputSource is = new InputSource(stream);
			is.setSystemId(systemId);
			return is;
		}

		private DefImpl aspectDef;

		private AspectImpl aspect;

		@Override
		public void startElement(String uri, String localName, String qName, Attributes attributes) {
			int lineNumber = locator.getLineNumber();
			characters = new StringBuilder();
			if ("def".equals(qName)) {
				aspectDef = new DefImpl(attributes.getValue("target"), lineNumber);
				result.addAspectDef(aspectDef);
			} else if ("aspect".equals(qName)) {
				aspect = createAspect(attributes);
				aspectDef.add(aspect);
			}
		}

		private StringBuilder characters = null;

		@Override
		public void characters(char[] ch, int start, int length) {
			characters.append(ch, start, length);
		}

		@Override
		public void endElement(String uri, String localName, String qName) {
			if ("aspect".equals(qName)) {
				aspect.setInterceptorStatement(characters.toString().trim());
			}
		}

		private AspectImpl createAspect(Attributes attributes) {
			int lineNumber = locator.getLineNumber();
			String pointcutPattern = attributes.getValue("pointcut");
			String accessModifier = attributes.getValue("modifier");
			String ignorePattern = attributes.getValue("not");
			boolean defaultPointcut = false;
			if (ignorePattern == null) {
				ignorePattern = "";
			}
			if (pointcutPattern == null || pointcutPattern.trim().length() == 0) {
				defaultPointcut = true;
				pointcutPattern = ".*";
				ignorePattern = "this," + ignorePattern;
			}
			String[] pointcutPatterns = pointcutPattern.split(",");
			String[] ignorePatterns = ignorePattern.split(",");
			AspectImpl aspect = new AspectImpl(pointcutPatterns, ignorePatterns, accessModifier, defaultPointcut, lineNumber);
			return aspect;
		}
	}

	public static class ConfigNotFoundException extends RuntimeException {

		private static final long serialVersionUID = -1940842714920243506L;

		public ConfigNotFoundException() {
			super();
		}

		public ConfigNotFoundException(String arg0, Throwable arg1) {
			super(arg0, arg1);
		}

		public ConfigNotFoundException(String arg0) {
			super(arg0);
		}

		public ConfigNotFoundException(Throwable arg0) {
			super(arg0);
		}

	}
}
